import java.util.ArrayList;
import java.util.List;

import akka.stm.Atomic;
import akka.stm.Ref;

/**
 * Represents a single transaction on the vending machine
 * 
 * @author SNelson
 * 
 */
public class VendingMachineTransaction extends Atomic<Boolean>
{
	/**
	 * The vending machine that this transaction will be executed on
	 */
	private final VendingMachine machine;
	/**
	 * The operations to perform in this transaction
	 */
	private final List<VendingOperation> operations;

	/**
	 * Private constructor
	 * 
	 * @param machine
	 *            The vending machine that will execute this transaction
	 * @param operations
	 *            The operations that will be performed during this transaction
	 */
	private VendingMachineTransaction(VendingMachine machine,
		List<VendingOperation> operations)
	{
		if (operations == null || machine == null)
			throw new NullPointerException();
		this.machine = machine;
		this.operations = operations;
	}

	/**
	 * The body of the transaction. Any operations on the refs inside will be
	 * rolled back if there is transaction collision
	 * 
	 * @return True if the transaction was successful, false otherwise
	 */
	@Override
	public Boolean atomically()
	{
		for (VendingOperation operation : operations)
		{
			boolean result = operation.run(machine.getState());
			if (!result)
				return false;
		}
		return true;
	}

	/**
	 * A builder for helping clients assemble a transaction. Allows clients to
	 * be unaware of STM and any implications of transaction rollback
	 * 
	 * @author SNelson
	 * 
	 */
	public static class Builder
	{
		/**
		 * The vending machine that the transaction will be executed on
		 */
		private final VendingMachine machine;
		/**
		 * The operations being built
		 */
		private List<VendingOperation> operations = new ArrayList<VendingOperation>();

		/**
		 * Constructor
		 * 
		 * @param machine
		 *            The vending machine that built transactions will be
		 *            executed on
		 */
		public Builder(VendingMachine machine)
		{
			this.machine = machine;
		}

		/**
		 * Private constructor
		 * 
		 * @param machine
		 *            The vending machine that built transactions will be
		 *            executed on
		 * @param operations
		 *            The operations in the builder so far
		 */
		private Builder(VendingMachine machine,
			List<VendingOperation> operations)
		{
			this.machine = machine;
			this.operations = operations;
		}

		/**
		 * Appends a purchase cookie operation to the transaction
		 * 
		 * @return The builder with the new operation added. Can be used for
		 *         chaining builder operations
		 */
		public Builder purchaseCookie()
		{
			operations.add(new VendingOperation()
			{
				@Override
				public Boolean run(Ref<VendingMachineState> state)
				{
					VendingMachineState curState = state.get();
					int cookieCount = curState.getCookieCount();
					if (cookieCount == 0)
						return false;
					state.swap(new VendingMachineState(
						curState.getCandyCount(), cookieCount - 1));
					return true;
				}
			});
			return new Builder(machine, operations);
		}

		/**
		 * Appends a purchase candy operation to the builder
		 * 
		 * @return The builder with the new operation appended. Can be used to
		 *         chain builder operations
		 */
		public Builder purchaseCandy()
		{
			operations.add(new VendingOperation()
			{
				@Override
				public Boolean run(Ref<VendingMachineState> state)
				{
					VendingMachineState curState = state.get();
					int candyCount = curState.getCandyCount();
					if (candyCount == 0)
						return false;
					state.swap(new VendingMachineState(candyCount - 1, curState
						.getCookieCount()));
					return true;
				}
			});
			return new Builder(machine, operations);
		}

		/**
		 * Appends a restock operation to the builder
		 * 
		 * @return The builder with the new operation appended. Can be used to
		 *         chain builder operations
		 */
		public Builder restock()
		{
			operations.add(new VendingOperation()
			{
				@Override
				public Boolean run(Ref<VendingMachineState> state)
				{
					state.swap(VendingMachine.INITIAL_STATE);
					return true;
				}
			});
			return new Builder(machine, operations);
		}

		/**
		 * Appends a abstract operation to the builder. This can be used to do
		 * anything that the client would like run during the transaction. For
		 * example a client can print a statement, or detect whether a
		 * transaction has been rolled back
		 * 
		 * @param runnable
		 *            The runnable to append to the transaction
		 * @return The builder with the new operation appended. Can be used to
		 *         chain builder operations
		 */
		public Builder doSomething(final Runnable runnable)
		{
			operations.add(new VendingOperation()
			{
				@Override
				public Boolean run(Ref<VendingMachineState> state)
				{
					runnable.run();
					return true;
				}
			});
			return new Builder(machine, operations);
		}

		/**
		 * Builds a transaction from the operations added via this builder
		 * 
		 * @return The transaction that was built
		 */
		public VendingMachineTransaction build()
		{
			return new VendingMachineTransaction(machine, operations);
		}
	}

	/**
	 * An interface for a generic operation that can be performed during a
	 * vending machine transaction
	 * 
	 * @author SNelson
	 * 
	 */
	private static interface VendingOperation
	{
		/**
		 * The body of the operation
		 * 
		 * @param state
		 *            A reference to the state of the vending machine.
		 *            Operations can modify this state and STM will protect the
		 *            transaction using ACI principles
		 * @return True if the operation was successful, false otherwise
		 */
		Boolean run(Ref<VendingMachineState> state);
	}
}
